feat: tri-mode BLE dongle - #1028
Conversation
16d1346 to
e9cf67a
Compare
Size Report
|
0667f5d to
f7478fd
Compare
f7478fd to
faa0bd8
Compare
faa0bd8 to
e698d5e
Compare
e698d5e to
138aee7
Compare
138aee7 to
86437ad
Compare
1637b06 to
9faaf4b
Compare
9faaf4b to
23e70aa
Compare
23e70aa to
f1cfb29
Compare
f1cfb29 to
ce48b03
Compare
15955c2 to
af7c0d3
Compare
af7c0d3 to
db930a2
Compare
db930a2 to
4f7846b
Compare
4f7846b to
8fc7872
Compare
8fc7872 to
c526262
Compare
c526262 to
5097a8c
Compare
5097a8c to
20b4a32
Compare
20b4a32 to
0722580
Compare
0722580 to
8291fa9
Compare
8291fa9 to
d2d34f5
Compare
d2d34f5 to
3c338e9
Compare
3c338e9 to
a70363d
Compare
a70363d to
a32f55f
Compare
a32f55f to
8f986cb
Compare
A BLE keyboard built with `rynk` can now be the peripheral half of a tri-mode dongle link: - a dedicated `DONGLE_PROFILE` bond slot (`NUM_BLE_PROFILE`) with its own `SwitchToDongle` key, so switching to the dongle never evicts a host bond; - dongle-seeking advertising (legacy 1M, non-discoverable, carrying the Rynk major) and directed re-advertising to a bonded dongle; - the `dongle_ctrl` characteristic: holding the dongle key for 5s notifies `DONGLE_CTRL_OPEN_PAIRING_WINDOW`, authorizing the dongle to pair another keyboard; - Apple connection-parameter tuning is skipped on the dongle profile, where the dongle owns the parameters.
A dongle binary (`dongle` feature) relays bonded RMK keyboards to a USB host: HID-over-GATT client toward each keyboard, Rynk GATT client toward the configuration target, and Rynk server on USB. Keymaps and storage stay on the keyboards; the dongle persists only its slot table (bond + name + recency). - `rmk::dongle`: `link` (one task per concurrent link: connect, secure, handshake, serve), `merge` (N keyboards into one HID report set), `router` (self-answers `0x09xx`, passes every other frame through byte-for-byte), and the pairing window (power-on, or authorized by a bonded keyboard). - Orthogonal to `rynk`/`vial`: one crate can build keyboard and dongle binaries side by side, each sizing its own BLE stack. - `examples/use_rust/nrf_dongle`: dongle (nRF54LM20A) + split central (nRF52833) + split peripheral (nRF54L15), the three-board setup this was validated on. Two constraints found on hardware and kept in comments: the keyboard's dongle-seeking advertisement must be legacy 1M (2M ext adv desyncs a legacy initiator), and peripheral latency must be 0 during pairing (the central's ~400ms P-256 pauses otherwise starve the link).
Adapts to the dense slot table and the folded probe, and removes the version handshake the probe change made redundant: - `slots_snapshot()` emits one entry per slot, `None` for unbonded, so the index is the slot number `SelectDongleTarget`/`ForgetDongleSlot` take. - `GetDongleSlots` takes over the `GetDongleInfo` arm, including the gate that keeps `DongleSlotsChange` muted until the host has read the table — which now reads better, since a push is a delta of that first read. - The version gate is gone. It killed the whole link on a Rynk major mismatch, but typing rides HOGP and doesn't care what config protocol the keyboard speaks, and the host reports a mismatch with a far better message once it connects through us. New pairings were never affected either way: the seeking advertisement carries `[0xD0, our major]`, so a mismatched keyboard is filtered at the scan — the gate only fired on a bonded keyboard whose firmware had been upgraded, exactly the case that should keep typing. - `version_bad` goes with it. The flag patched a loop the probe itself created: fail → disconnect → `claim()` reselects the slot → reconnect → fail. No probe, no loop, no flag. - The name now comes from the GAP Device Name (0x2A00) — the same value RMK publishes as `DeviceInfo::product_name`, but a standard characteristic rather than a Rynk payload. A failed or truncated read leaves the slot unnamed instead of dropping the link; that part is load-bearing, since a `GetDeviceInfo` that fails to decode would otherwise rebuild the same redial loop with no flag left to stop it. The dongle now issues zero Rynk requests and depends only on the frozen frame header, so payload types can evolve without a dongle firmware update. The seq space belongs entirely to the host, retiring the "pass-through only opens after the handshake, so no seq can collide" ordering constraint.
8f986cb to
f93bc52
Compare
The dongle itself: a BLE central that pairs with RMK keyboards and relays them to the host over USB. HID reports are merged across links (modifiers OR'd, keycodes unioned), and the Rynk config session is routed — 0x09xx dongle commands answered locally, everything else passed through to the selected keyboard over its rynk GATT service, with topic pushes forwarded back.
rmk::dongle: slot table with bond persistence and Lamport-recency eviction; per-link state machine (claim → connect → secure → handshake → serve) with LESC Just Works pairing gated by an RSSI-selected 30 s pairing window; HID merge; the USB frame router.Dongle::new(controller, address, slots)owns its stack, sized byDONGLE_LINKS_NUM;slotscome fromnew_storage_for_dongle.UsbTransport::with_dongle_routerattaches the router. Thedonglefeature is orthogonal torynk/vial: one crate can build keyboard and dongle binaries side by side (CI covers the union feature set).examples/use_rust/nrf_dongle: tri-board reference — nRF54LM20A dongle, nRF52833 split central, nRF54L15 split peripheral.Hardware-tested end to end on the tri-board rig: pairing via
SwitchToDongle, typing through the split (both halves), config pass-through over USB, and bonded directed reconnect after a dongle power-cycle. Design doc included (zh + en).Stack 4/4 — depends on #1027.